require 'rubygems'
require 'fsdb'
require 'zip/zip'
require 'find'
require File.dirname(__FILE__) + '/const'

class Installr::Database::Storage
  def initialize(log, options={})
    @log = log
    @options = options.dup
    @db_dir = File.expand_path(@options[:data_base_dir]+ '/' + Installr::Database::STORAGE_BASENAME, __FILE__)
    puts "database directory path is #{@db_dir}" if @options[:verbose]
  end

  def browse_dir(path)
    @db.browse path do |entries|
      entries.each do |entry|
        yield entry
      end
    end
  end

  def browse_each_child(path)
    @db.browse_dir path do |child_path|
      @db.browse child_path do |child_object|
        yield child_path, child_object
      end
    end
  end

  def open
    @db = FSDB::Database.new(@db_dir)
    rescue FSDB::Database::CreateFileError
      puts "[storage:open] cannot create database #{db_dir}"
      @log.error("[storage:open] cannot create database #{db_dir}")
      exit(false)
  end

  def create_agent(agent_name, agent_ip_address, user, password, pem, passphrase, port, remote_upload_path)
    unless @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name]
      @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name] = [agent_ip_address, user, password, pem, passphrase, port, remote_upload_path]
      return 0
    else
      return -1
    end
  end

  def delete_agent(agent_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name]
      # remove from the agent pool
      @db.delete(Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name)
      # remove from every group pools
      dirs = []
      browse_each_child(Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH) do |dir, obj|
        dirs << dir
      end
      dirs.each do |dir|
        @db.edit dir do |record|
          record.delete_if { |row| row == agent_name }
        end
      end
      return 0
    else
      return -1
    end
    rescue FSDB::Database::MissingObjectError
      return 0
  end

  def list_agents(group_name)
    arr = []
    if group_name
      if @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
        @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name].last(@db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name].length - 1).each { |field| arr << field }
      else
        return nil
      end
    else
      browse_dir(Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH) { |dir| arr << dir }
    end

    return arr
    rescue FSDB::Database::MissingObjectError
      return nil
  end

  def info_agent(agent_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name]
      obj = @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name]
      # [0: agent_ip_address, 1: user, 2: password, 3: pem, 4: passphrase, 5: port, 6: remote_upload_path]
      return [obj[0], obj[1], obj[2], obj[3], obj[4], obj[5], obj[6]]
    else
      return nil
    end
  end

  def create_group(group_name, group_description)
    unless @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
      @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name] = [group_description]
      return 0
    else
      return -1
    end
  end

  def delete_group(group_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
      @db.delete(Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name)
      return 0
    else
      return -1
    end
  end

  def list_groups
    arr = []
    browse_dir(Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH) { |dir| arr << dir }
    return arr
    rescue FSDB::Database::MissingObjectError
      return nil
  end

  def info_group(group_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
      obj = @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
      return [obj[0]]
    else
      return nil
    end
  end

  def check_agent(agent_name)
    @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name]
  end

  def check_group(group_name)
    @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
  end

  def check_app(app_name, agent_name)
    @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name]
  end

  def check_deployment(agent_name, app_name, md5hash)
    @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash]
  end

  def add_agent(agent_name, group_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name] and
        @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
      @db.edit Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name do |group|
        group << agent_name
      end
      return 0
    else
      return -1
    end
  end

  def remove_agent(agent_name, group_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::AGENT_PATH+'/'+agent_name] and
        @db[Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name]
      @db.edit Installr::Database::TARGET_PATH+Installr::Database::GROUP_PATH+'/'+group_name do |record|
        record.delete_if { |row| row == agent_name }
      end
      return 0
    else
      return -1
    end
  end

  def create_agent_deployment(agent_name, app_name, time_stamp, war_file, md5hash, remote_upload_path)
    unless @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash]
      @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash] = [time_stamp, war_file, md5hash, agent_name, remote_upload_path]
      @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+'0'] = [time_stamp, war_file, md5hash, agent_name, remote_upload_path]
      return 0
    else
      return -1
    end
  end

  def delete_agent_deployment(agent_name, app_name)
    unless @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name]
       return -1
    end
    elements = []
    @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name].each do |element|
      elements << element
    end
    elements.each do |element|
      @db.delete(Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+element)
    end
    @db.delete(Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name)
    return 0
  end

  def change_agent_deployment(agent_name, app_name, md5hash)
      if @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash]
        target_obj = @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash]
        @db.delete(Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+'0')
        @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+'0'] = target_obj
        return 0
      else
        return -1
      end
  end

  def info_agent_deployment(agent_name, app_name, md5hash)
    if @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash]
      obj = @db[Installr::Database::DEPLOY_PATH+'/'+agent_name+'/'+app_name+'/'+md5hash]
      return [obj[0], obj[1], obj[2], obj[3]]
    else
      return nil
    end
  end

  def delete_app_deployment(app_name)
    agents = []
    browse_dir(Installr::Database::DEPLOY_PATH) { |dir| agents << dir }
    agents.each do
      |agent|
      if @db[Installr::Database::DEPLOY_PATH+'/'+agent+'/'+app_name]
        self.delete_agent_deployment(agent, app_name)
      end
    end
    return 0
  end

  def history_app_deployment(app_name, head_only = 0)
    agents = []
    objs = []
    browse_dir(Installr::Database::DEPLOY_PATH) { |dir| agents << dir }
    agents.each do
      |agent|
      if @db[Installr::Database::DEPLOY_PATH+'/'+agent+'/'+app_name]
        if head_only == 0
          browse_dir(Installr::Database::DEPLOY_PATH+'/'+agent+'/'+app_name) { |dir| objs << @db[Installr::Database::DEPLOY_PATH+'/'+agent+'/'+app_name+'/'+dir] if dir != '0' }
        else
          browse_dir(Installr::Database::DEPLOY_PATH+'/'+agent+'/'+app_name) { |dir| objs << @db[Installr::Database::DEPLOY_PATH+'/'+agent+'/'+app_name+'/'+dir] if dir == '0' }
        end
      end
    end
    return objs
  end

  def list_apps
    agents = []
    apps = []
    browse_dir(Installr::Database::DEPLOY_PATH) { |dir| agents << dir }
    agents.each { |agent| browse_dir(Installr::Database::DEPLOY_PATH+'/'+agent) { |dir| apps << dir } }
    return apps
    rescue FSDB::Database::MissingObjectError
      return nil
  end


  def create_scenario(scenario_name, scenario_script)
    unless @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name]
      @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name] = [scenario_script]
      return 0
    else
      return -1
    end
  end

  def delete_scenario(scenario_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name]
      schedules = list_schedules
      schedules.each do
        |schedule|
        scenarios = list_scenarios(schedule)
        scenarios.each do
          |scenario|
          remove_scenario(scenario, schedule) if scenario_name == scenario
        end
      end
      @db.delete(Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name)
      return 0
    else
      return -1
    end
  end

  def list_scenarios(schedule_name)
    arr = []
    if schedule_name
      if @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
        @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name].last(@db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name].length - 2).each { |field| arr << field }
      else
        return nil
      end
    else
      browse_dir(Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH) { |dir| arr << dir }
    end
    return arr
    rescue FSDB::Database::MissingObjectError
      return nil
  end

  def info_scenario(scenario_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name]
      obj = @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name]
      return [obj[0]]
    else
      return nil
    end
  end

  def create_schedule(schedule_name, cron_format)
    unless @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name] = ['D', cron_format]
      return 0
    else
      return -1
    end
  end

  def delete_schedule(schedule_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      @db.delete(Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name)
      return 0
    else
      return -1
    end
    rescue FSDB::Database::MissingObjectError
      return 0
  end

  def list_schedules
    arr = []
    browse_dir(Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH) { |dir| arr << dir }

    return arr
    rescue FSDB::Database::MissingObjectError
      return nil
  end

  def info_schedule(schedule_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      obj = @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      return obj
    else
      return nil
    end
  end

  def add_scenario(scenario_name, schedule_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name] and
        @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      scenarios = list_scenarios(schedule_name)
      scenarios.each do
        |scenario|
	return -1 if scenario_name == scenario
      end

      @db.edit Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name do |group|
        group << scenario_name
      end
      return 0
    else
      return -1
    end
  end

  def remove_scenario(scenario_name, schedule_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCENARIO_PATH+'/'+scenario_name] and
        @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      @db.edit Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name do |record|
        record.delete_if { |row| row == scenario_name }
      end
      return 0
    else
      return -1
    end
  end

  def activate_schedule(schedule_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      @db.edit Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name do |group|
        group[0] = 'A'
      end
      return 0
    else
      return -1
    end
  end

  def deactivate_schedule(schedule_name)
    if @db[Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name]
      @db.edit Installr::Database::TARGET_PATH+Installr::Database::SCHEDULE_PATH+'/'+schedule_name do |group|
        group[0] = 'D'
      end
      return 0
    else
      return -1
    end
  end

  def unzip(zip, unzip_dir, remove_after = false)
    Zip::ZipFile.open(zip) do |zip_file|
      zip_file.each do |f|
        f_path=File.join(unzip_dir, f.name)
        FileUtils.mkdir_p(File.dirname(f_path))
        zip_file.extract(f, f_path) unless File.exist?(f_path)
      end
    end
    FileUtils.rm(zip) if remove_after
    end

    def zip(dir, zip_dir, remove_after = false)
    Zip::ZipFile.open(zip_dir, Zip::ZipFile::CREATE) do |zipfile|
      Find.find(dir) do |path|
        Find.prune if File.basename(path)[0] == ?.
        dest = /#{dir}\/(\w.*)/.match(path)
        # Skip files if they exists
        begin
          zipfile.add(dest[1],path) if dest
        rescue Zip::ZipEntryExistsError
        end
      end
    end
    FileUtils.rm_rf(dir, :secure=>true) if remove_after
    return 0
    rescue
      puts "[storage:zip] cannot create the zip file #{zip_dir}"
      @log.error("[storage:zip] cannot create the zip file #{zip_dir}")
      return -1
  end

  def export(export_path, remove_after)
    zip(@db_dir, export_path, remove_after)
  end

  def import(import_path, remove_before = false)
    if File.exist?(import_path)
      FileUtils.rm_rf(@db_dir, :secure=>true) if remove_before
      open
      unzip(import_path, @db_dir)
    else
      puts "[storage:import] cannot find import_path #{import_path}"
      @log.error("[storage:import] cannot find import_path #{import_path}")
      return -1
    end
    return 0
  end
end
